package com.md.util;

import com.md.model.excel.CellFontStyleInfo;
import com.md.model.excel.CellStyleInfo;
import com.md.model.excel.MergeInfo;
import com.md.model.html.CustomLabel;
import com.md.model.html.LabelAttribute;
import com.md.model.html.LabelStyle;
import com.md.model.html.TD;
import com.md.value.ConvertType;
import com.md.value.MergeType;
import com.md.value.html.FontStyle;
import com.md.value.html.FontWeight;
import com.md.value.html.LabelAttributeType;
import com.md.value.html.LabelStyleType;
import com.md.value.html.TextDecoration;
import com.md.value.html.Unit;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.DateUtil;
import org.apache.poi.xssf.model.StylesTable;
import org.apache.poi.xssf.usermodel.XSSFCell;
import org.apache.poi.xssf.usermodel.XSSFColor;
import org.apache.poi.xssf.usermodel.XSSFRow;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTColor;

import java.io.IOException;
import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;

import static com.md.value.ReadFileConstants.MAX_RGB;
import static com.md.value.ReadFileConstants.MAX_TINT;
import static com.md.value.html.HtmlConstants.DEFAULT_TABLE;
import static com.md.value.html.HtmlConstants.TR_START;

/**
 * @author 刘骄阳
 * 2024-05-14 1:24
 */
public class ExcelUtil {

  //要让Apache POI在一定程度上具备EasyExcel的优点，特别是减少内存使用和提高处理大数据量时的性能，可以采取以下策略：
  //使用SXSSF（Streaming Usermodel API）： Apache POI提供了SXSSF（Streaming Usermodel API），
  // 它类似于EasyExcel的流式处理方式，能够将数据写入磁盘而不是全部保存在内存中。使用SXSSF模式可以显著降低内存使用，
  // 尤其是在处理大量数据时。SXSSF的工作原理是仅保留最近操作的行在内存中，其余数据则直接写入硬盘上的临时文件。
  //   SXSSFWorkbook workbook = new SXSSFWorkbook(100); // 保持100行在内存中
  //   // 然后像操作普通Workbook一样使用workbook
  // 分批读取数据： 虽然POI默认不是流式读取，但可以通过手动控制每次读取的数据量来模拟流式处理，减少内存占用。
  // 例如，可以逐个sheet或分块读取行数据，处理完一部分数据后立即释放资源，而不是一次性加载整个工作簿。
//优化数据结构： 在使用POI读取数据时，尽量减少不必要的数据复制，比如直接处理单元格数据而不是创建额外的对象副本。
//及时释放资源： 在读写操作完成后，及时关闭Workbook、Sheet、Row和Cell等资源，避免内存泄漏。
//使用最新版本的POI： 新版本的Apache POI可能包含性能改进和内存优化，定期更新到最新版本有助于获得更好的性能。
//自定义实现： 根据具体需求，可以考虑自定义实现部分功能，比如仅读取必要的列或行，或者开发一个中间层来管理数据读写过程中的内存使用。
//虽然上述方法可以提升POI在处理大数据时的性能和内存效率，但它们可能需要更多的手动配置和代码实现，
// 相比EasyExcel的开箱即用来说，复杂度有所增加。在选择方案时，应根据项目具体需求和团队熟悉度来决定。

  public static void readExcel(final XSSFWorkbook workbook) {
    // 第一版,先不考虑性能问题.
    // 工作簿数量
    int sheetCount = workbook.getNumberOfSheets();
    StylesTable stylesTable = workbook.getStylesSource();
    for (int i = 0; i < sheetCount; i++) {
      XSSFSheet sheet = workbook.getSheetAt(i);
      int startRowIndex = sheet.getFirstRowNum();
      int endRowIndex = sheet.getLastRowNum();
      convertToHtml(startRowIndex, endRowIndex, sheet, stylesTable, "font", ConvertType.EXCEL_TO_HTML);
      System.out.println();
      for (int j = startRowIndex; j < endRowIndex; j++) {
        XSSFRow row = sheet.getRow(j);
        int startCellIndex = row.getFirstCellNum();
        int endCellIndex = row.getLastCellNum();
        for (int k = startCellIndex; k < endCellIndex; k++) {
          XSSFCell cell = row.getCell(k);
          CellStyleInfo cellStyleInfo = CellStyleInfo.readCellStyle(cell, stylesTable);
          System.out.println();
        }
      }
    }
  }

  public static void main(String[] args) throws IOException {
    XSSFWorkbook book = new XSSFWorkbook("/Volumes/SD/test_副本.xlsx");
    XSSFSheet sheetAt = book.getSheetAt(0);
    String s = convertToHtml(sheetAt.getFirstRowNum(), sheetAt.getLastRowNum(), sheetAt, book.getStylesSource(), "font", null);
    System.out.println();
  }

  public static String convertToHtml(final Integer rowStartIndex, final Integer rowEndIndex,
                                     final XSSFSheet sheet, final StylesTable stylesTable,
                                     final String label, final ConvertType convertType) {
    List<MergeInfo> mergeInfos = MergeInfo.readMergeInfoList(sheet.getMergedRegions());
    Map<String, MergeInfo> mergeMap = mergeInfos.stream().collect(Collectors.toMap(mergeInfo ->
      mergeInfo.getRowIndex() + "_" + mergeInfo.getCellIndex(), Function.identity()));
    StringBuilder htmlContent = new StringBuilder(DEFAULT_TABLE);
    for (int i = rowStartIndex; i < rowEndIndex; i++) {
      htmlContent.append(TR_START);
      XSSFRow row = sheet.getRow(i);
      int startCellIndex = row.getFirstCellNum();
      int endCellIndex = row.getLastCellNum();
      for (int j = startCellIndex; j < endCellIndex; j++) {
        MergeInfo mergeInfo = mergeMap.get(i + "_" + j);
        if (mergeInfo != null && mergeInfo.getType() == MergeType.CONTINUE) {
          continue;
        }
        // 解析样式
        CellStyleInfo cellStyle = CellStyleInfo.readCellStyle(row.getCell(j), stylesTable);
        List<CustomLabel> customLabels = buildCustomLabel(label, cellStyle.getFontStyle(),
          cellStyle.getCellContentStyleList());
        String content = customLabels.stream().map(baseLabel -> HtmlUtil.generateHTMLCode(label, baseLabel.getStyles(),
          baseLabel.getAttributes(), baseLabel.getContent())).collect(Collectors.joining());

        // 获取单元格样式
        TD td = buildTD(cellStyle, mergeInfo);
        htmlContent.append(HtmlUtil.generateHTMLCode(td.getLabel(), td.getStyles(), td.getAttributes(), content));
      }
      htmlContent.append("</tr>");
    }
    htmlContent.append("</table>");
    return htmlContent.toString();
  }

  /**
   * 应用附加颜色
   *
   * @param tint 附加颜色
   * @param rgb  r|g|b
   * @return 添加了附加色彩的r|g|b
   */
  public static byte applyTint(final double tint, final byte rgb) {
    double iRgb;
    if (rgb < 0) {
      iRgb = MAX_RGB + rgb;
    } else {
      iRgb = rgb;
    }
    if (tint < 0) {
      // Lum * (1.0 + tint)
      return (byte) (iRgb * (MAX_TINT + tint));
    } else if (tint > 0) {
      // Lum = Lum * (1.0-tint) + (HLSMAX - HLSMAX * (1.0-tint))
      return (byte) (iRgb * (MAX_TINT - tint) + (MAX_RGB - MAX_RGB * (MAX_TINT - tint)));
    }
    return (byte) iRgb;
  }


  /**
   * 字节转为16进制
   *
   * @param byteArray 字节数组
   * @return 16进制
   */
  public static String byteArrayToHexStr(final byte[] byteArray) {
    if (byteArray == null) {
      return null;
    }
    StringBuilder sb = new StringBuilder(byteArray.length * 2);
    for (byte b : byteArray) {
      sb.append(String.format("%02X", b));
    }
    return sb.toString().toUpperCase();
  }

  public static CTColor getBorderCtColor(final XSSFColor xssfColor) {
    return Optional.ofNullable(xssfColor).map(XSSFColor::getCTColor).orElse(null);
  }

  public static String getCellValue(final Cell cell) {
    if (cell == null) {
      return "";
    }
    // 空白或空
    CellType cellType = cell.getCellType();
    switch (cellType) {
      case NUMERIC:
        // 数值
        if (DateUtil.isCellDateFormatted(cell)) {
          //判断是否为日期类型
          return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(
            Date.from(cell.getLocalDateTimeCellValue().atZone(ZoneId.systemDefault()).toInstant()));
        } else {
          // 否则当数字转换
          DecimalFormat df = new DecimalFormat("####.####");
          return df.format(cell.getNumericCellValue());
        }
      case STRING:
        // 字符串
        return getStringValue(cell, false);
      case BLANK:
        // 空值
        return null;
      case BOOLEAN:
        // 布尔值
        return String.valueOf(cell.getBooleanCellValue());
      case _NONE:
      case ERROR:
        // 错误
        break;
      case FORMULA:
        // 公式型
        break;
      default:
        break;
    }
    // 错误类型
    return cell.getCellFormula();
  }

  /**
   * 读取字符串
   *
   * @param cell        列
   * @param judgeLength 是否校验长度(true: 校验; false: 不校验)
   * @return 字符串
   */
  public static String getStringValue(final Cell cell, final Boolean judgeLength) {
    if (cell == null) {
      return "";
    }
    try {
      String value = new BigDecimal(String.valueOf(cell)).stripTrailingZeros().toPlainString();
      if (Boolean.TRUE.equals(judgeLength) && value.length() != cell.getStringCellValue().length()) {
        return cell.getStringCellValue();
      } else {
        return value;
      }
    } catch (Exception e) {
      if (cell.getCellType() == CellType.ERROR) {
        return "";
      }
      return cell.getStringCellValue();
    }
  }


  public static TD buildTD(final CellStyleInfo cellStyle, final MergeInfo mergeInfo) {
    TD td = new TD();
    Optional.ofNullable(mergeInfo).ifPresent(merge -> {
      if (merge.getMergeRowCount() != 0) {
        td.addAttribute(LabelAttribute.of(LabelAttributeType.ROWSPAN, merge.getMergeRowCount()));
      }
      if (merge.getMergeCellCount() != 0) {
        td.addAttribute(LabelAttribute.of(LabelAttributeType.COLSPAN, merge.getMergeCellCount()));
      }
    });

    List<LabelStyle> styles = new ArrayList<>();
    // 获取字体样式
    Optional.ofNullable(cellStyle.getFontStyle()).ifPresent(fontStyle -> {
      styles.add(LabelStyle.of(LabelStyleType.FONT_SIZE,
        fontStyle.getFormatFontSize(ConvertType.EXCEL_TO_HTML), Unit.PX));
      // 字体粗细
      if (fontStyle.getIsBold()) {
        styles.add(LabelStyle.of(LabelStyleType.FONT_WEIGHT, FontWeight.BOLD.getValue()));
      }
      // 字体倾斜
      if (fontStyle.getIsItalic()) {
        styles.add(LabelStyle.of(LabelStyleType.FONT_STYLE, FontStyle.ITALIC.getValue()));
      }
      // 字体颜色
      if (fontStyle.getIsColor()) {
        styles.add(LabelStyle.of(LabelStyleType.COLOR, "#" + fontStyle.getColor().getColor()));
      }
      // 字体风格
      Optional.ofNullable(fontStyle.getFontName()).ifPresent(fontName -> {
        styles.add(LabelStyle.of(LabelStyleType.FONT_FAMILY, fontName));
      });
    });
    // 背景色
    if (cellStyle.getForegroundColor() != null && cellStyle.getForegroundColor().getColor() != null) {
      styles.add(LabelStyle.of(LabelStyleType.BACKGROUND_COLOR, "#" + cellStyle.getForegroundColor().getColor()));
    }
    styles.forEach(td::addStyle);
    return td;
  }

  public static List<CustomLabel> buildCustomLabel(final String label,
                                                   final CellFontStyleInfo cellStyle,
                                                   final List<CellFontStyleInfo> cellContentStyleList) {
    List<CustomLabel> childrenLabels = new ArrayList<>();
    if (cellContentStyleList.isEmpty()) {
      // 说明没有单独的字体样式,设置整个样式
      CustomLabel customLabel = new CustomLabel(label);
      customLabel.setContent(cellStyle.getContent());
      childrenLabels.add(customLabel);
      return childrenLabels;
    }
    cellContentStyleList.forEach(cellFontStyle -> {
      CustomLabel customLabel = new CustomLabel(label);
      List<LabelStyle> styles = new ArrayList<>();
      // 字体大小
      styles.add(LabelStyle.of(LabelStyleType.FONT_SIZE,
        cellFontStyle.getFormatFontSize(ConvertType.EXCEL_TO_HTML), Unit.PX));
      // 字体粗细
      processCellStyle(cellStyle.getIsBold(), cellFontStyle.getIsBold(),
        () -> styles.add(LabelStyle.of(LabelStyleType.FONT_WEIGHT, FontWeight.NORMAL.getValue())),
        () -> styles.add(LabelStyle.of(LabelStyleType.FONT_WEIGHT, FontWeight.BOLD.getValue())));
      // 字体倾斜
      processCellStyle(cellStyle.getIsItalic(), cellFontStyle.getIsItalic(),
        () -> styles.add(LabelStyle.of(LabelStyleType.FONT_STYLE, FontStyle.NORMAL.getValue())),
        () -> styles.add(LabelStyle.of(LabelStyleType.FONT_STYLE, FontStyle.ITALIC.getValue())));
      // 字体颜色
      if (cellStyle.getIsColor()) {
        // 说明单元格有设置颜色, 内层肯定有颜色, 判断两个颜色是否一致
        if (!cellStyle.getColor().getColor().equals(cellFontStyle.getColor().getColor())) {
          // 说明内层颜色和外层颜色不一致, 设置样式
          styles.add(LabelStyle.of(LabelStyleType.COLOR, "#" + cellFontStyle.getColor().getColor()));
        }
      } else if (cellFontStyle.getIsColor()) {
        // 说明外层没有设置颜色, 判断内层是否有颜色
        styles.add(LabelStyle.of(LabelStyleType.COLOR, "#" + cellFontStyle.getColor().getColor()));
      }

      // 字体风格
      Optional.ofNullable(cellFontStyle.getFontName()).ifPresent(fontName -> {
        styles.add(LabelStyle.of(LabelStyleType.FONT_FAMILY, fontName));
      });

      // 删除线和下划线
      StringBuilder textDecoration = new StringBuilder();
      if (cellFontStyle.getIsUnderLine()) {
        // 判断单元格或者单元格文字是否有下划线
        textDecoration.append(TextDecoration.UNDERLINE.getCode());
      }
      if (cellFontStyle.getIsStrikeOut()) {
        // 判断单元格或者单元格文字是否有删除线
        textDecoration.append(" ").append(TextDecoration.LINE_THROUGH.getCode());
      }
      if (textDecoration.length() != 0) {
        styles.add(LabelStyle.of(LabelStyleType.TEXT_DECORATION, textDecoration.toString()));
      }
      styles.forEach(customLabel::addStyle);
      customLabel.setContent(cellFontStyle.getContent());
      childrenLabels.add(customLabel);
    });
    return childrenLabels;
  }

  private static void processCellStyle(final boolean cellStyleStatus, final boolean cellFontStyleStatus,
                                       final Runnable runnable1, final Runnable runnable2) {
    if (cellStyleStatus && !cellFontStyleStatus) {
      // 判断内层是否与外层有冲突
      runnable1.run();
    } else if (!cellStyleStatus && cellFontStyleStatus) {
      // 判断内层是否有设置样式
      runnable2.run();
    }
  }
}
